package com.hqd.ch03.v41.web.servlet.support;

import com.hqd.ch03.v41.web.servlet.AbstractHandlerExceptionResolver;
import com.hqd.ch03.v41.web.servlet.ModelAndView;
import com.hqd.ch03.v41.web.utils.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * 简单异常处理器
 */
public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {
    public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";

    /**
     * 异常映射，key为异常全类名，value为视图名。例：
     * key：java.lang.RuntimeException  value： "error"
     */
    private Properties exceptionMappings;

    /**
     * 不需要处理的异常
     */
    private Class<?>[] excludedExceptions;

    /**
     * 出现没有配置的异常时，默认视图
     */
    private String defaultErrorView;

    /**
     * 出现没有对应的响应码时，默认响应码
     */
    private Integer defaultStatusCode;
    /**
     * 视图和响应码的映射
     */
    private Map<String, Integer> statusCodes = new HashMap<>();


    private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;

    public void setExceptionMappings(Properties mappings) {
        this.exceptionMappings = mappings;
    }

    public void setExcludedExceptions(Class<?>... excludedExceptions) {
        this.excludedExceptions = excludedExceptions;
    }

    public void setDefaultErrorView(String defaultErrorView) {
        this.defaultErrorView = defaultErrorView;
    }

    public void addStatusCode(String viewName, int statusCode) {
        this.statusCodes.put(viewName, statusCode);
    }

    public Map<String, Integer> getStatusCodesAsMap() {
        return Collections.unmodifiableMap(this.statusCodes);
    }

    public Map<String, Integer> getStatusCodes() {
        return statusCodes;
    }

    public void setStatusCodes(Properties statusCodes) {
        for (Enumeration<?> enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements(); ) {
            String viewName = (String) enumeration.nextElement();
            Integer statusCode = Integer.valueOf(statusCodes.getProperty(viewName));
            this.statusCodes.put(viewName, statusCode);
        }
    }

    public void setDefaultStatusCode(int defaultStatusCode) {
        this.defaultStatusCode = defaultStatusCode;
    }

    public void setExceptionAttribute(String exceptionAttribute) {
        this.exceptionAttribute = exceptionAttribute;
    }

    @Override
    protected ModelAndView doResolveException(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

        //根据异常找到对应的视图
        String viewName = determineViewName(ex, request);
        if (viewName != null) {
            // 找到视图后，开始查找对应的响应码
            Integer statusCode = determineStatusCode(request, viewName);
            if (statusCode != null) {
                //设置对应的状态
                applyStatusCodeIfPossible(request, response, statusCode);
            }
            //获取对应的视图
            return getModelAndView(viewName, ex, request);
        } else {
            return null;
        }
    }

    protected String determineViewName(Exception ex, HttpServletRequest request) {
        String viewName = null;
        if (this.excludedExceptions != null) {
            for (Class<?> excludedEx : this.excludedExceptions) {
                if (excludedEx.equals(ex.getClass())) {
                    return null;
                }
            }
        }
        if (this.exceptionMappings != null) {
            viewName = findMatchingViewName(this.exceptionMappings, ex);
        }
        if (viewName == null && this.defaultErrorView != null) {
            viewName = this.defaultErrorView;
        }
        return viewName;
    }


    protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
        String viewName = null;
        String dominantMapping = null;
        int deepest = Integer.MAX_VALUE;
        for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements(); ) {
            String exceptionMapping = (String) names.nextElement();
            int depth = getDepth(exceptionMapping, ex);
            if (depth >= 0 && (depth < deepest || (depth == deepest &&
                    dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) {
                deepest = depth;
                dominantMapping = exceptionMapping;
                viewName = exceptionMappings.getProperty(exceptionMapping);
            }
        }
        return viewName;
    }

    protected int getDepth(String exceptionMapping, Exception ex) {
        return getDepth(exceptionMapping, ex.getClass(), 0);
    }

    private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
        if (exceptionClass.getName().contains(exceptionMapping)) {
            return depth;
        }
        if (exceptionClass == Throwable.class) {
            return -1;
        }
        return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
    }

    protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
        if (this.statusCodes.containsKey(viewName)) {
            return this.statusCodes.get(viewName);
        }
        return this.defaultStatusCode;
    }

    protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) {
        /**
         * 不是include的话，设置状态
         */
        if (!WebUtils.isIncludeRequest(request)) {
            response.setStatus(statusCode);
            request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);
        }
    }

    protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {
        return getModelAndView(viewName, ex);
    }

    protected ModelAndView getModelAndView(String viewName, Exception ex) {
        ModelAndView mv = new ModelAndView(viewName);
        if (this.exceptionAttribute != null) {
            //添加异常信息
            mv.addObject(this.exceptionAttribute, ex);
        }
        return mv;
    }

}